using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Xml.Serialization;
using EnvDTE;
using Microsoft.VisualStudio.Shell.Interop;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Windows.Input;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Zip;

namespace WebAppUploader
{
	public class ProjectProfileModel : INotifyPropertyChanged 
	{
		public event PropertyChangedEventHandler PropertyChanged;

		#region Property Name
		private string _Name = "";
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue("")]
		[System.ComponentModel.Category("Category")]
		public string Name
		{
			get
			{
				return _Name;
			}
			set
			{
				_Name = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Name"));
			}
		}

		#endregion

		#region Property IISHost
		private string _IISHost = "";
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue("")]
		[System.ComponentModel.Category("Category")]
		public string IISHost
		{
			get
			{
				return _IISHost;
			}
			set
			{
				_IISHost = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("IISHost"));
			}
		}

		#endregion

		#region Property Port
		private int _Port = 8112;
		public int Port
		{
			get
			{
				return _Port;
			}
			set
			{
				_Port = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Port"));
			}
		}

		#endregion
	
		#region Property Username
		private string _Username = "";
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue("")]
		[System.ComponentModel.Category("Category")]
		public string Username
		{
			get
			{
				return _Username;
			}
			set
			{
				_Username = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Username"));
			}
		}

		#endregion

		#region Property Password
		private string _Password = "";
		[XmlIgnore]
		public string Password
		{
			get
			{
				return _Password;
			}
			set
			{
				_Password = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Password"));
			}
		}

		#endregion

		public string PasswordSaved {
			get {
				if (string.IsNullOrWhiteSpace(Password))
					return "";
				var d = ProtectedData.Protect(System.Text.Encoding.UTF8.GetBytes(Password), null, DataProtectionScope.CurrentUser);
				return Convert.ToBase64String(d);
			}
			set {
				if (string.IsNullOrWhiteSpace(value))
					return;
				var d = ProtectedData.Unprotect(Convert.FromBase64String(value), null, DataProtectionScope.CurrentUser);
				Password = System.Text.Encoding.UTF8.GetString(d);
			}
		}

		#region Property Secure
		private bool _Secure = false;
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue(false)]
		[System.ComponentModel.Category("Category")]
		public bool Secure
		{
			get
			{
				return _Secure;
			}
			set
			{
				_Secure = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Secure"));
			}
		}

		#endregion

		#region Property UploadConfig
		private bool _UploadConfig = false;
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue(false)]
		[System.ComponentModel.Category("Category")]
		public bool UploadConfig
		{
			get
			{
				return _UploadConfig;
			}
			set
			{
				_UploadConfig = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("UploadConfig"));
			}
		}

		#endregion

		#region Property UploadBin
		private bool _UploadBin = true;
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue(true)]
		[System.ComponentModel.Category("Category")]
		public bool UploadBin
		{
			get
			{
				return _UploadBin;
			}
			set
			{
				_UploadBin = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("UploadBin"));
			}
		}

		#endregion

		#region Property CanUpload
		private bool _CanUpload = true;
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue(false)]
		[System.ComponentModel.Category("Category")]
		public bool CanUpload
		{
			get
			{
				return _CanUpload;
			}
			set
			{
				_CanUpload = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("CanUpload"));
			}
		}

		#endregion

		#region Property SelectCommand
		private ICommand _SelectCommand = null;
		[XmlIgnore]
		public ICommand SelectCommand
		{
			get
			{
				return _SelectCommand;
			}
		}
		#endregion


		public ProjectFileCollection _Files = new ProjectFileCollection();
		[XmlIgnore]
		public ProjectFileCollection Files {
			get {
				return _Files;
			}
		}

		public ProjectFileModel[] FileArray {
			get {
				return _Files.ToArray();
			}
			set {
				_Files.Clear();
				foreach (var item in value)
				{
					_Files.Add(item);
				}
			}
		}

		#region Property UploadCommand
		private ICommand _UploadCommand = null;
		[XmlIgnore]
		public ICommand UploadCommand
		{
			get
			{
				return _UploadCommand;
			}
		}
		#endregion

		#region Property CancelCommand
		private ICommand _CancelCommand = null;
		[XmlIgnore]
		public ICommand CancelCommand
		{
			get
			{
				return _CancelCommand;
			}
		}

		#endregion

		#region Property RefreshCommand
		private ICommand _RefreshCommand = null;
		[XmlIgnore]
		public ICommand RefreshCommand
		{
			get
			{
				return _RefreshCommand;
			}
		}

		#endregion

		internal Project project { get; set; }

		#region Property Status
		private string _Status = "";
		[System.ComponentModel.Bindable(true)]
		[System.ComponentModel.DefaultValue("")]
		[System.ComponentModel.Category("Category")]
		public string Status
		{
			get
			{
				return _Status;
			}
			set
			{
				_Status = value;
				if (PropertyChanged != null)
					PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Status"));
			}
		}

		#endregion
	

		public ProjectProfileModel()
		{
			_UploadCommand = new ActionCommand<object>(OnUploadCommand);
			_CancelCommand = new ActionCommand<object>(OnCancelCommand);
			_RefreshCommand = new ActionCommand<object>(x=>RefreshFiles());
			_SelectCommand = new ActionCommand<object>(OnSelectCommand);
		}

		private void OnSelectCommand(object p) {
			if (p == null)
				return;
			bool s = p.ToString().ToLower() == "true";

			if (s)
			{
				foreach (var item in Files)
				{
					item.LastUpdate = DateTime.MinValue;
				}
				RefreshFiles();
			}
			else {
				foreach (var item in Files)
				{
					item.Dirty = false;
				}
			}
		}

		private void OnUploadCommand(object p) {
			this.RefreshFiles();


			ProjectSyncModel.Current.Save();


			ThreadPool.QueueUserWorkItem(x =>
			{
				SafeInvoke(Upload);
			});
		}

		private void OnCancelCommand(object p)
		{
		}


		#region private void RefreshFiles()
		private void RefreshFiles()
		{


			LoadProjectItem("", project.ProjectItems);

			// Mark Dirty...

			foreach (var item in Files)
			{
				FileInfo diskFile = new FileInfo(item.FilePath);
				if (diskFile.LastWriteTime != item.LastUpdate)
				{
					item.LastUpdate = diskFile.LastWriteTime;
					if (item.FileName.EndsWith(".config") && !this.UploadConfig)
						continue;
					item.Dirty = true;
				}
			}

			if (this.UploadBin)
			{
				DirectoryInfo bin = new DirectoryInfo(Path.GetDirectoryName(project.FullName) + "\\bin");
				LoadFolder("bin", bin);
			}


			// Remove Duplicates...
			var f = Files.ToArray();
			foreach (var item in f)
			{
				if (Files.Count(x => string.Compare( x.FilePath ,item.FilePath,true)==0) > 1)
				{
					Files.Remove(item);
				}
			}

			ProjectSyncModel.Current.Save();

			Files.Refresh();
		}

		#region private void LoadFolder(string p,DirectoryInfo bin)
		private void LoadFolder(string root, DirectoryInfo rootDir)
		{
			if (!rootDir.Exists)
				return;

			foreach (var item in rootDir.EnumerateFiles())
			{
				var f = _Files.FirstOrDefault(x => x.FilePath == item.FullName);
				if (f == null) {
					f = new ProjectFileModel { FilePath = item.FullName, FileName = item.Name, RelativePath = root };
					_Files.Add(f);
				}
				if (item.LastWriteTime != f.LastUpdate) {
					f.LastUpdate = item.LastWriteTime;
					f.Dirty = true;
				}
			}

			foreach (var item in rootDir.EnumerateDirectories())
			{
				LoadFolder(root + "\\" + item.Name, item);
			}
		}
		#endregion




		private void LoadProjectItem(string root, ProjectItems items)
		{
			if (items == null)
				return;
			foreach (ProjectItem item in items)
			{

				Property prop = GetBuildAction(item);
				if (prop != null && prop.Value != null && prop.Value.ToString() == "2")
				{
					string fp = item.FileNames[1];
					var f = _Files.FirstOrDefault(x => string.Compare( x.FilePath , fp)==0);
					if (f == null)
					{
						f = new ProjectFileModel { FilePath = fp, FileName = item.Name };
						f.RelativePath = root;
						_Files.Add(f);
					}
				}

				string folderName = "";

				if (item.Kind == "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}" || item.Kind == "{6BB5F8F0-4483-11D3-8BCF-00C04F8EC28C}")
					folderName = item.Name + "\\";

				LoadProjectItem(root + folderName , item.ProjectItems);

			}
		}

		private static Property GetBuildAction(ProjectItem item)
		{
			try
			{
				Property prop = item.Properties.Item("BuildAction");
				return prop;
			}
			catch (Exception ex)
			{
				Trace.WriteLine(ex.ToString());
				return null;
			}

		}
		#endregion

		private ObservableCollection<ZipFilePart> _Parts = new ObservableCollection<ZipFilePart>();
		[XmlIgnore]
		public ObservableCollection<ZipFilePart> Parts
		{
			get
			{
				return _Parts;
			}
		}

		public void Invoke(Action action) {
			ProjectSyncModel.Dispatcher.Invoke(action);
		}


		public void SafeInvoke(Action action) {
			try
			{
				action();
			}
			catch (Exception ex) {
				Trace.WriteLine(ex.ToString());
				MessageBox.Show(ex.Message, "Error");
			}
		}
	

		#region private void Upload(ProjectProfileModel profile)
		private void Upload()
		{

			SetStatus("Initializing");

			WebClient webClient = new WebClient();

			//webClient.Credentials = new NetworkCredential(Username, Password);
			webClient.UseDefaultCredentials = true;
			webClient.BaseAddress = string.Format("{0}://{1}:8112", Secure ? "https" : "http", IISHost);
			CredentialCache cache = new CredentialCache();
			cache.Add(new Uri(webClient.BaseAddress), "Negotiate", new NetworkCredential(Username, Password));
			webClient.Credentials = cache;

			try
			{
				Invoke(() =>
				{
					CanUpload = false;
				});

				SetStatus("Authenticating");

				string session = webClient.UploadString("/File/NewSession", "nothing");

				SetStatus("Creating Zip");

				using (TempFolder temp = new TempFolder(session))
				{
					string zip = temp.CreateTempFile("Upload.zip");

					CreateZipFile(zip);

					SetStatus("Dividing Zip");

					// divide in parts of 4 mega bytes...
					CreateParts(temp, zip);

					SetStatus("Uploading...");

					ParallelOptions options = new ParallelOptions
					{
						MaxDegreeOfParallelism = 2
					};

					Parallel.ForEach(_Parts.ToArray(), x => UploadFile(x, session));

					string idList = String.Join(",", Parts.Select(x => x.PartID).ToArray());

					SetStatus("Expanding...");

					string result = webClient.UploadString("/File/Deploy?sid=" + session + "&indexList=" + idList, "nothing");

					SetStatus("Done");

					
				}



				Invoke(() =>
				{
					foreach (var item in Files)
					{
						item.Dirty = false;
					}
					Files.Refresh();
					_Parts.Clear();
				});
			}
			finally
			{
				Invoke(() =>
				{
					CanUpload = true;
				});
			}
		}

		#region private void SetStatus(string p)
		private void SetStatus(string p)
		{
			Invoke(() => {
				Status = p;
			});
		}
		#endregion


		#region private object UploadFile(ZipFilePart x,string p)
		private void UploadFile(ZipFilePart part, string session)
		{
			WebClient webClient = new WebClient();

			//webClient.Credentials = new NetworkCredential(Username, Password);
			webClient.UseDefaultCredentials = true;
			webClient.BaseAddress = string.Format("{0}://{1}:8112", Secure ? "https" : "http", IISHost);
			CredentialCache cache = new CredentialCache();
			cache.Add(new Uri(webClient.BaseAddress), "Negotiate", new NetworkCredential(Username, Password));
			webClient.Credentials = cache;

			Invoke(() =>
			{
				part.Progress = 0;
			});

			AutoResetEvent wait = new AutoResetEvent(false);

			webClient.UploadProgressChanged += (s, e) =>
			{
				Invoke(() =>
				{
					part.Progress = e.ProgressPercentage * 2;
				});
			};
			webClient.UploadFileCompleted += (s, e) =>
			{
				Invoke(() =>
				{
					if (e.Error != null)
					{
						part.Error = e.Error.ToString();
					}
					part.Progress = 100;
				});
				wait.Set();
			};
			webClient.UploadFileAsync(new Uri("/File/Upload?sid=" + session + "&index=" + part.PartID.ToString(), UriKind.Relative), part.FilePath);
			wait.WaitOne();

		}
		#endregion



		#region private void CreateParts(string zip)
		private void CreateParts(TempFolder temp, string zip)
		{
			Invoke(() =>
			{
				_Parts.Clear();
			});
			byte[] buffer = new byte[1024 * 1024 * 2];
			int index = 1;
			using (FileStream fs = File.OpenRead(zip))
			{
				int count = 0;
				while ((count = fs.Read(buffer, 0, buffer.Length)) > 0)
				{
					ZipFilePart part = new ZipFilePart();
					part.PartID = index++;
					part.FilePath = temp.CreateTempFile(part.PartID + ".dat");
					part.FileSize = count;
					part.Host = IISHost;

					File.WriteAllBytes(part.FilePath, NewBuffer(buffer, count));

					Invoke(() =>
					{
						_Parts.Add(part);
					});

				}
			}
		}

		#region private byte[] NewBuffer(byte[] buffer,int count)
		private byte[] NewBuffer(byte[] buffer, int count)
		{
			if (buffer.Length == count)
				return buffer;
			byte[] copy = new byte[count];
			Buffer.BlockCopy(buffer, 0, copy, 0, count);
			return copy;
		}
		#endregion

		#endregion


		private void CreateZipFile(string zip)
		{

			// strip duplicates...
			using (FileStream fs = File.OpenWrite(zip))
			{
				using (ZipOutputStream zipFile = new ZipOutputStream(fs))
				{
					foreach (var item in Files.Where(x => x.Dirty).ToArray())
					{
						//string path = GetRelativePath(item.FilePath);
						if (File.Exists(item.FilePath))
						{
							//zipFile.Add(item.FilePath, item.RelativePath + "\\" + item.FileName);
							string fileName = "\\" + item.FileName;
							if (!string.IsNullOrWhiteSpace(item.RelativePath)) {
								if (item.RelativePath.EndsWith("\\")) {
									fileName = item.RelativePath + item.FileName;
								}
								else
								{
									fileName = item.RelativePath + "\\" + item.FileName;
								}
							}
							ZipEntry entry = new ZipEntry(fileName);
							zipFile.PutNextEntry(entry);

							Trace.WriteLine("Added: " + entry.Name);
							using (FileStream r = File.OpenRead(item.FilePath))
							{
								r.CopyTo(zipFile);
							}
						}
						else
						{
							Invoke(() =>
							{
								Files.Remove(item);
							});
						}
					}
					Trace.WriteLine("Saving Zip");
					zipFile.Close();
				}
			}
		}
		#endregion

		//public string ProjectFolder
		//{
		//    get
		//    {
		//        return Path.GetDirectoryName(project.FullName);
		//    }
		//}

		//#region private string GetRelativePath(string p)
		//private string GetRelativePath(string p)
		//{
		//    if (!p.StartsWith(ProjectFolder))
		//        throw new ArgumentOutOfRangeException("file name must be within folder...");
		//    p = p.Substring(ProjectFolder.Length);
		//    if (p.StartsWith("\\"))
		//        p = p.Substring(1);
		//    return Path.GetDirectoryName(p);
		//}
		//#endregion

	}

	public class ProjectFileCollection : ObservableCollection<ProjectFileModel> {
		public void Refresh() {
			base.OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs(System.Collections.Specialized.NotifyCollectionChangedAction.Reset));
		}
	}

}
